import logging
import logging.config
import os
import sys

# 使用 pyinstaller 打包后,路径不对,使用该方式获取可用的配置文件目录 start
if getattr(sys, 'frozen', False):
    absPath = os.path.dirname(os.path.abspath(sys.executable))
elif __file__:
    absPath = os.path.dirname(os.path.abspath(__file__))
logsdir = absPath + os.sep + "logs"
print(f"logsdir={logsdir}")
if not os.path.exists(logsdir):  # 检查logs目录是否存在
    os.makedirs(logsdir)  # 如果不存在则创建logs目录

# '读取日志配置文件'
# logging.fileConfig('./logging.conf')
fname='./logging.conf'
# logging.config.fileConfig(fname, defaults=None, disable_existing_loggers=True)
# 打开文件并指定使用 UTF-8 编码
with open(fname, 'r', encoding='utf-8') as f:
    logging.config.fileConfig(f, defaults=None, disable_existing_loggers=True)

import time

from funcall import execute_shell_fun_call
from funcall.FuncallInfo import FuncallInfo
from funcalltools.json_utils import to_json
from utils.OpenAiData import Usage
from utils.dict_utils import get_dict_value



from funcall.AbstractFunCall import funcall_log, fun_call_log_debug

mainerror_log = logging.getLogger("mainerror")
allFinish=False

# 设置 logging 级别为 DEBUG
# logging.basicConfig(level=logging.DEBUG)
# logging.basicConfig(
#     level=logging.DEBUG,  # 修改日志级别
#     # level=logging.WARN,  # 修改日志级别
#     format='%(asctime)s %(name)s [%(pathname)s line:%(lineno)d] %(levelname)s %(message)s',
#     datefmt='%Y-%m-%d %H:%M:%S',  # 日期时间格式
#     filename='app.log',  # 日志文件名
#     filemode='w'  # 日志模式，如：w：写、a+：追加写 等
# )
funcall_log.setLevel(logging.DEBUG)

from openai import OpenAI
from datetime import datetime
import json

import funcall
from conf.config import AppConf


# url="http://localhost:11434/api/generate"
# url="https://api.deepseek.com"
# api_key = os.environ.get('DEEPSEEK_API_KEY')
# model="deepseek-r1:7b"

# 阿里云
# model="qwen-turbo-latest"
# url="https://dashscope.aliyuncs.com/compatible-mode/v1"
# api_key = os.environ.get('DASHSCOPE_API_KEY')
# print("api_key="+api_key)


base_url=AppConf['ai']['base_url']
api_key=AppConf['ai']['api_key']
model=AppConf['ai']['model']

client = OpenAI(
    # 若没有配置环境变量，请用百炼API Key将下行替换为：api_key="sk-xxx",
    api_key=api_key,
    base_url=base_url,  # 填写DashScope SDK的base_url
)

# 定义工具列表，模型在选择使用哪个工具时会参考工具的name和description
tools = [
    # 工具1 获取当前时刻的时间
    {
        "type": "function",
        "function": {
            "name": "get_current_time",
            "description": "当你想知道现在的时间时非常有用。",
            # 因为获取当前时间无需输入参数，因此parameters为空字典
            "parameters": {}
        }
    },
    # 工具2 获取指定城市的天气
    {
        "type": "function",
        "function": {
            "name": "get_current_weather",
            "description": "当你想查询指定城市的天气时非常有用。",
            "parameters": {
                "type": "object",
                "properties": {
                    # 查询天气时需要提供位置，因此参数设置为location
                    "location": {
                        "type": "string",
                        "description": "城市或县区，比如北京市、杭州市、余杭区等。"
                    }
                }
            },
            "required": [
                "location"
            ]
        }
    }
]
tools = []
fcs=funcall.fcs()
for fc in fcs:
    funNode={
        "type": "function",
        "function":vars(fc)
    }
    tools.append(funNode)
    # funcalltools.append(fc.to_json())


# 模拟天气查询工具。返回结果示例：“北京今天是雨天。”
def get_current_weather(location):
    return f"{location}今天是雨天。 "

# 查询当前时间的工具。返回结果示例：“当前时间：2024-04-15 17:15:18。“
def get_current_time():
    # 获取当前日期和时间
    current_datetime = datetime.now()
    # 格式化当前日期和时间
    formatted_time = current_datetime.strftime('%Y-%m-%d %H:%M:%S')
    # 返回格式化后的当前时间
    return f"当前时间：{formatted_time}。"

# 封装模型响应函数
def get_response(messages):
    # print(f"{funcalltools}")
    completion = client.chat.completions.create(
        model=model,
        messages=messages,
        tools=tools
        )
    return completion.model_dump()

# def call_openai(messages):
#     get_response(messages)

def call_with_messages(msgs):
    usage = Usage()
    print('\n')
    messages = [ ]
    for msg in msgs:
        messages.append(msg)

    print("-"*60)
    # 模型的第一轮调用
    i = 1
    first_response = get_response(messages)
    res_choice = first_response['choices'][0]
    usage.addTokens(first_response['usage'])
    assistant_output = res_choice['message']
    funcallInfo = FuncallInfo(type='api',description="API调用完成",remark=f'第{i}轮大模型', req_args=assistant_output['content'],log_text=to_json(first_response))
    funcallInfo.completion_tokens=usage.completion_tokens
    funcallInfo.prompt_tokens=usage.prompt_tokens
    funcallInfo.total_tokens=usage.total_tokens
    fun_call_log_debug(funcallInfo)
    print(f"\n第{i}轮大模型输出信息：{assistant_output}\n")
    if  assistant_output['content'] is None:
        assistant_output['content'] = ""
    messages.append(assistant_output)
    # 如果不需要调用工具，则直接返回最终答案
    if assistant_output['tool_calls'] == None:  # 如果模型判断无需调用工具，则将assistant的回复直接打印出来，无需进行模型的第二轮调用
        print(f"无需调用工具，我可以直接回复：{assistant_output['content']}")
        return
    #请求之后删除上次调用的funcall
    callIdList=[]
    # 如果需要调用工具，则进行模型的多轮调用，直到模型判断无需调用工具
    while assistant_output['tool_calls'] != None:
        for res_call in assistant_output['tool_calls']:
            res_fun = res_call['function']
            f_name= res_fun['name']
            for fc in fcs:
                if f_name==fc.name:
                    tool_info = {"name": f_name, "role": 'tool'}
                    tool_info['id'] = res_call['id']
                    callIdList.append(res_call['id'])
                    tool_info['content']=fc.call(json.loads(res_fun['arguments']))
                    break

            print(f"工具输出信息：{tool_info['content']}\n")
            print("-"*60)
            # 只发送必要的工具调用信息
            messages.append(tool_info)
            response_tmp = get_response(messages)
            assistant_output = response_tmp['choices'][0]['message']
            i += 1
            print(f"第{i}轮大模型输出信息：{assistant_output}\n")
            usage.addTokens(response_tmp['usage'])
            funcallInfo = FuncallInfo(type='api',description="API调用完成", remark=f'第{i}轮大模型',
                                  req_args=assistant_output['content'], log_text=to_json(response_tmp))
            usage_tmp=Usage(response_tmp['usage'])
            funcallInfo.completion_tokens = usage_tmp.completion_tokens
            funcallInfo.prompt_tokens = usage_tmp.prompt_tokens
            funcallInfo.total_tokens = usage_tmp.total_tokens
            fun_call_log_debug(funcallInfo)
            if assistant_output['content'] is None:
                assistant_output['content'] = ""
            #删除了他会重复上一步的事情,
            # delete_contain_tool_call_id_messages(callIdList,messages)
            messages.append(assistant_output)
            if i>2500:
                # mainerror_log.warning("超过50次循环,下班休息一下")
                # 休息5秒
                time.sleep(50)
                # return
    print(f"最终答案：{assistant_output['content']}")
    funcallInfo = FuncallInfo(type='api',description="API调用完成",remark=f'共调用{i}次大模型', req_args=assistant_output['content'],log_text=to_json(assistant_output))
    funcallInfo.completion_tokens=usage.completion_tokens
    funcallInfo.prompt_tokens=usage.prompt_tokens
    funcallInfo.total_tokens=usage.total_tokens
    fun_call_log_debug(funcallInfo)

    global allFinish
    if '***AI-FINISH[是否继续]AI-FINISH***' not in assistant_output['content']:
        allFinish = True

    # if '***AI-FINISH[我已经完成了所有工作]AI-FINISH***' in assistant_output['content']:
    #     allFinish =True
    # elif '***AI-FINISH[我已经尽力了]AI-FINISH***' in assistant_output['content']:
    #     allFinish =True
    # elif '***AI-FINISH[是否继续]AI-FINISH***' in assistant_output['content']:
    #     allFinish = False

    # """
    #         如果你全部完成了,请回复:***AI-FINISH[我已经完成了所有工作]AI-FINISH***
    #         如果你已经无能为力了,请回复:***AI-FINISH[我已经尽力了]AI-FINISH***
    #         如果你觉想询问我是否还需要继续,请回复: ***AI-FINISH[是否继续]AI-FINISH***
    #  """

def delete_contain_tool_call_id_messages(callIdList,messages):
    for callId in callIdList:
        # 删除上次调用的fun call
        for i in range(len(messages) - 1, -1, -1):
            if 'id' in messages[i] and messages[i]['id'] == callId:
                del messages[i]
            elif 'tool_calls' in messages[i]:
                for tool_call in messages[i]['tool_calls']:
                    if tool_call['id'] in callIdList:
                        del messages[i]
                        break


def run_in_win():
    msg = "杭州天气如何,现在几点了"
    msg = "创建一个hello.txt格式文件"
    msg = "创建一个java项目,使用solon框架"
    msg = "帮我编写一个'二分查找算法'的java类,并且使用脚本进行编译后运行"
    msg = "帮我编写一个java类,并且使用脚本进行编译后运行"
    # msg="""运行 BinarySearch.class 类"""
    msg = "创建一个java项目,使用springboot框架,编写一个web服务端,并使用mvn进行编译和测试"
    msg = ("帮我写个使用 Flask-SQLAlchemy 连接 mysql数据库的 python3 代码,并进行测试运行"
           ",并且写验证代码运行验证是否达到预期效果."
           "你可以使用任何工具,不完成不要停止回答")

    msgs = [{"content": msg,  # 提问示例："现在几点了？" "一个小时后几点" "北京天气如何？"
             "role": "user"
             }]

    msgs = [{"content": msg,  # 提问示例："现在几点了？" "一个小时后几点" "北京天气如何？"
             "role": "user"
             }]

    msg = ("任务编号为001:"
           "创建一个java项目,使用springboot框架,编写一个完整的ERP的web服务端,服务端要求使用redis来存储token,mybatis+mysql进行操作数据库"
           "完成之后使用mvn进行编译和单元测试"
           ",并且写验证代码运行验证是否达到预期效果."
           "完整的ERP包含很多模块,你自己去搜索不要敷衍我,"
           "你可以使用任何工具,不完成不要停止工作")

    msg = ("任务编号为001:"
           "创建一个java项目,使用springboot框架,编写一个完整的ERP的web服务端,服务端要求使用redis来存储token,mybatis+mysql进行操作数据库"
           "完成之后使用mvn进行编译和单元测试"
           ",并且写验证代码运行验证是否达到预期效果."
           "完整的ERP包含很多模块,你自己去搜索不要敷衍我,"
           "所有工具都是在你的电脑上安装的,不完成不要停止工作")

    # logging.debug(AppConf)
    dir_work = get_dict_value(AppConf, 'ai.work_dir') + os.sep
    # 系统角色用于为聊天助手分配特定的行为或上下文，以影响对话的模型行为。例如，您可以将系统角色分配给聊天助手，并设置内容为特定主题或行为，以便ChatGPT在对话中表现特定的个性或专业知识。
    # ['system', 'assistant', 'user', 'tool', 'function']
    msgs = [
        {
            # "content": f"你是一个AI打工仔,你要完成我指派的任务,无特殊情况,请优先使用中文,数据库配置信息在文件 {dir_work}app.yaml中,可以使用任何的工具",
            "content": f"如果你全部完成了,请回复:***AI-FINISH[我已经完成了所有工作]AI-FINISH***",
            "role": "system"
        },
        {
            # "content": f"你是一个AI打工仔,你要完成我指派的任务,无特殊情况,请优先使用中文,数据库配置信息在文件 {dir_work}app.yaml中,可以使用任何的工具",
            "content": f"请优先使用中文,代码中注释也一样用中文,数据库配置信息请到文件 {dir_work}app.yaml中读取",
            "role": "system"
        },

        # {
        #     "content": "你是一个AI打工仔,你要完成我指派的任务,无特殊情况,请优先使用中文,数据库配置信息在文件app.yaml中,可以使用任何的工具",
        #     "role": "system"
        # },
        # {
        #     "content": "java项目单独新建一个文件夹,不要放到Z:\\aiwork主目录,"
        #                "在你设计好方案之后,存放到Z:\\aiwork\\programme.md 文件中,"
        #                "帮你的工作进度,以及工作过程中创建的文件名和目录名都存放到 Z:\\aiwork\\step.md 文件中,"
        #                "下次让你做同一个任务的时候可以调取这些信息来继续未完成的工作,"
        #                "按照原先的方案和进度继续开始,"
        #                "在你开始工作之前,请先检查 Z:\\aiwork\\programme.md 和 Z:\\aiwork\\step.md 文件是否存在,是否有未完成的工作,"
        #                "如果有,请继续完成未完成的工作,"
        #                "如果没有或者无法读取工作进度文件,则表示这是一个新的项目",
        #     "role": "system"
        # },
        {"content": msg,  # 提问示例："现在几点了？" "一个小时后几点" "北京天气如何？"
         "role": "user"
         }
    ]


    call_with_messages(msgs)
# 循环500次
# for _ in range(1):
#     try:
#         call_with_messages(msgs)
#     except Exception as e:
#         mainerror_log.error(f"{e}")



def run_in_linux():

    msg = ("任务编号为001:"
           "创建一个java项目,使用springboot框架,编写一个完整的ERP的web服务端,服务端要求使用redis来存储token,mybatis+mysql进行操作数据库"
           "完成之后使用mvn进行编译和单元测试"
           ",并且写验证代码运行验证是否达到预期效果."
           "完整的ERP包含很多模块,你自己去搜索不要敷衍我,"
           "所有工具都是在你的电脑上安装的,不完成不要停止工作")

    # msg="你先安装好jdk8,请调用shell工具"
    # msg="写个java类,并且使用脚本进行编译后运行"
    # msg=("现在交给你一项艰巨的任务,写一个压缩软件,要比现有的压缩软件更高的压缩率,而且压缩速度不能太慢"
    #      "不需要你能马上完成,你可以写几天,写几个月,直到你测试已经完成了,才可以结束工作"
    #      "或者你先初步设计个方案,以及工作步骤放到 /aiwork/linux/comp.md 文件中"
    #      "方案要详细,工作步骤要详细,工作步骤要详细,工作步骤要详细,工作步骤要详细,工作步骤要详细,工作步骤要详细,工作步骤要详细,工作步骤要详细,工作步骤要详细,工作步骤要详细,工作步骤要详细,工作步骤要详细,工作步骤要详细,工作步骤要详细,工作步骤要详细,工作步骤要详细,工作步骤要详细,工作步骤要详细,工作步骤要详细")

    # msg="帮我用java写一个md5的解码程序,写完之后需要你进行测试"

    # msg ="5d41402abc4b2a76b9719d911017c592 这是一个md5加密后的字符串,请你用java解码,编译完成即可"
    msg = "用java连接数据库,并查询数据库都有哪些表,使用maven管理jar包,并运行测试"

    dir_work = str(get_dict_value(AppConf, 'ai.work_dir')) + os.sep
    # 系统角色用于为聊天助手分配特定的行为或上下文，以影响对话的模型行为。例如，您可以将系统角色分配给聊天助手，并设置内容为特定主题或行为，以便ChatGPT在对话中表现特定的个性或专业知识。
    # ['system', 'assistant', 'user', 'tool', 'function']
    msgs = [
        {
            # 加了有效果,可以配合 allFinish 变量进行循环调用
            "content": """
                如果你全部完成了,请回复:***AI-FINISH[我已经完成了所有工作]AI-FINISH***
                如果你已经无能为力了,请回复:***AI-FINISH[我已经尽力了]AI-FINISH***
                如果你觉想询问我是否还需要继续,请回复: ***AI-FINISH[是否继续]AI-FINISH***
                       """,
            "role": "system"
        },
        {
            # "content": f"你是一个AI打工仔,你要完成我指派的任务,无特殊情况,请优先使用中文,数据库配置信息在文件 {dir_work}app.yaml中,可以使用任何的工具",
            "content": f"你是一个AI打工仔,你要完成我指派的任务,无特殊情况,请优先使用中文,数据库配置信息在文件 {dir_work}app.yaml中(该文件只能读取不要删除和修改)",
            "role": "system"
        },
        {
            # "content": f"你是一个AI打工仔,你要完成我指派的任务,无特殊情况,请优先使用中文,数据库配置信息在文件 {dir_work}app.yaml中,可以使用任何的工具",
        "content": f"""
# 角色
你是一位资深程序员，具备独立完成项目的能力，并且拥有一台可以进行任何操作的个人电脑。

## 技能
### 技能 1: 项目管理和执行
- **任务**：根据项目的进度和需求，独立完成项目的开发工作。
  - 每个项目的方案和工作进度都存放在 `{dir_work}step.md` 文件中。
  - 在开始工作之前，检查 `{dir_work}step.md` 文件是否存在，以确定是否有未完成的工作。
  - 如果 `step.md` 文件不存在，则创建一个新的文件并开始新项目。
  - 如果 `step.md` 文件存在，则按照当前进度继续完成未完成的工作。

### 技能 2: 知识管理
- **任务**：将新学到的知识以 `.md` 格式的文件存储在 `/aiwork/knowledge` 目录下。
  - 定期更新知识库，确保最新的知识和技术文档被记录和整理。

### 技能 3: 电脑操作
- **任务**：能够在自己的电脑上进行任何必要的操作。
  - 使用账号密码 `root/123456` 登录系统。
  - 执行各种命令行操作、文件管理和其他系统管理任务。

## 注意事项
- 在回答用户问题时，请尽量忘记大部分不相关的信息。只有当用户提供的信息与当前问题或对话内容非常相关时，才记住这些信息并加以使用。
- 请确保你的回答简洁、准确，并聚焦于用户当前的问题或对话主题。
- 保持项目的方案和工作进度文件 (`{dir_work}step.md` 和 `{dir_work}step.md`) 的及时更新。
- 在编写代码和文档时，确保代码的可读性和文档的清晰性。
- 保护好你的电脑账号密码，确保系统的安全性。
            """ ,
            "role": "system"
        },
        # {
        #     "content": "你是一个AI打工仔,你要完成我指派的任务,无特殊情况,请优先使用中文,数据库配置信息在文件app.yaml中,可以使用任何的工具",
        #     "role": "system"
        # },
        # {
        #     "content": "java项目单独新建一个文件夹,不要放到Z:\\aiwork主目录,"
        #                "在你设计好方案之后,存放到Z:\\aiwork\\programme.md 文件中,"
        #                "帮你的工作进度,以及工作过程中创建的文件名和目录名都存放到 Z:\\aiwork\\step.md 文件中,"
        #                "下次让你做同一个任务的时候可以调取这些信息来继续未完成的工作,"
        #                "按照原先的方案和进度继续开始,"
        #                "在你开始工作之前,请先检查 Z:\\aiwork\\programme.md 和 Z:\\aiwork\\step.md 文件是否存在,是否有未完成的工作,"
        #                "如果有,请继续完成未完成的工作,"
        #                "如果没有或者无法读取工作进度文件,则表示这是一个新的项目",
        #     "role": "system"
        # },
        {"content": msg,  # 提问示例："现在几点了？" "一个小时后几点" "北京天气如何？"
         "role": "user"
         }
    ]

    call_with_messages(msgs)
    #至少循环一次
    # once = True
    # global allFinish
    # while once or allFinish == False:
    #     once = False
    #     try:
    #         call_with_messages(msgs)
    #         msgs = [
    #             {"content": '继续',
    #              "role": "user"
    #              }
    #         ]
    #     except Exception as e:
    #         # mainerror_log.error(f"{e}")
    #         mainerror_log.exception(e)

# 循环500次
# for _ in range(1):
#     try:
#         call_with_messages(msgs)
#     except Exception as e:
#         mainerror_log.error(f"{e}")

if __name__ == '__main__':
    run_in_win()
    # run_in_linux()
